﻿module Kmean

open System
open System.IO
open System.Collections.Generic
open MathNet.Numerics
open MathNet.Numerics.FSharp
open System.Diagnostics
open MathNet.Numerics.LinearAlgebra.Double

//set point as default decimal separator
System.Threading.Thread.CurrentThread.CurrentCulture <- System.Globalization.CultureInfo.CreateSpecificCulture("En")
System.Threading.Thread.CurrentThread.CurrentUICulture <- System.Globalization.CultureInfo.CreateSpecificCulture("En")

type ImageData = 
  {
    X : DenseVector
    Y : DenseVector
    mutable prev_cluster: int
    mutable current_cluster : int
  }

let text = File.ReadAllText("semeion.data")
let dimensions = 256
let mutable optimum_reached = false
let dataList = text.Split(' ','\n') |> Seq.toList |> List.filter(fun s -> s <> "" && s <> "\r")
let (data_vector:List<ImageData>) = ResizeArray()
let seed = Random()
Console.WriteLine("Insert the number of clusters")
let k = Console.ReadLine() |> Convert.ToInt32
Console.WriteLine("Insert the directory of the output file")
let mutable output_directory = Console.ReadLine()
if output_directory.EndsWith("\\") |> not then
  output_directory <- output_directory + "\\"

let rec gather_data (data:string list) counter x_vector =
  let rec gather_labels (l: 'a list) steps res =
    if steps = 0 then
      (l,res)
    else
      gather_labels l.Tail (steps - 1) (res @ [l.Head])

  if data = [] then
    ()
  else
    if counter = dimensions then
      let l,y = gather_labels data 10 []
      let labels = y |> List.map(fun y1 -> y1 |> Double.Parse)
      let image_component =
        {
          X = DenseVector(x_vector |> List.toArray)
          Y = DenseVector(labels |> List.toArray)
          prev_cluster = -1
          current_cluster = -1
        }
      data_vector.Add(image_component)
      gather_data l 0 []
    else
      gather_data data.Tail (counter + 1) (x_vector @ [data.Head |> Convert.ToDouble])

gather_data dataList 0 []
let n = data_vector |> Seq.length

let (mu_vector:List<DenseVector>) = ResizeArray()
for i = 1 to k do
  let random_set = ResizeArray()
  for i = 0 to n - 1 do
    random_set.Add(data_vector.[i])
  let random = seed.Next(0,n)
  let random_vector = random_set.[random].X
  random_set.RemoveAt(random)
  mu_vector.Add(random_vector)

let assign_cluster (v:ImageData) =
  let mutable min_index = 1
  let mutable min_distance = (v.X - mu_vector.[0]) * (v.X - mu_vector.[0])
  for i = 2 to k do
    let cluster_distance = (v.X - mu_vector.[i - 1]) * (v.X - mu_vector.[i - 1])
    if cluster_distance < min_distance then
      min_distance <- cluster_distance
      min_index <- i
  if min_index <> v.current_cluster then
    optimum_reached <- false
  v.current_cluster <- min_index

let mu (k:int) =
  let cluster_elements = data_vector |> Seq.filter(fun e -> e.current_cluster = k)
  let mutable num_sum = DenseVector(dimensions)
  for v in cluster_elements do
    num_sum <- num_sum + v.X
  num_sum / ((cluster_elements |> Seq.length) |> float)

let mutable iterations = 0
let timer = new Stopwatch()
Console.WriteLine("Computing K-Means, please wait, it might take several seconds")
timer.Start()
while not optimum_reached do
  iterations <- iterations + 1
  optimum_reached <- true
  for i = 0 to n - 1 do
    data_vector.[i].prev_cluster <- data_vector.[i].current_cluster
  for i = 1 to n do
    assign_cluster data_vector.[i - 1]
  for i = 1 to k do
    mu_vector.[i - 1] <- mu i
timer.Stop()
Console.WriteLine("Done!")

Console.WriteLine("Computing Rand index...")
let mutable a = 0
let mutable b = 0
for i = 1 to n do
  let x1 = data_vector.[i - 1]
  for j = 1 to n do
    let x2 = data_vector.[j - 1]
    if i <> j then
      if x1.Y = x2.Y && x1.current_cluster = x2.current_cluster then
        a <- a + 1
      elif x1.Y <> x2.Y && x1.current_cluster <> x2.current_cluster then
        b <- b + 1

let rand_index = 2.0 * ((float a) + (float b)) / ((float n) * ((float n) - 1.0))
Console.WriteLine("Done!")

Console.WriteLine("Creating output file in " + output_directory)
Directory.CreateDirectory(output_directory) |> ignore
let output_text = ResizeArray()
for i = 0 to k - 1 do
  output_text.Add(("u[" + i.ToString() + "] = [" + mu_vector.[i].ToString() + "]"))

File.WriteAllLines(output_directory + "kmeans_out_" + k.ToString() + ".out",(output_text |> Seq.toList) @ 
  ["Clusters = " + k.ToString()] @
  ["Elapsed time: " + (timer.ElapsedMilliseconds / (1000 |> int64)).ToString() + " seconds"] @
  ["Iterations: " + iterations.ToString()] @
  ["Avarage time per iteration: " + ((timer.ElapsedMilliseconds / (1000 |> int64) |> float) / (float iterations)).ToString() + " seconds" ] @
  ["Rand index: " + rand_index.ToString()])
Console.WriteLine("Done!")